package com.violet.utils;

import com.violet.annotation.verify.VCanNull;
import com.violet.annotation.verify.VNot;
import com.violet.annotation.verify.VPhone;
import com.violet.annotation.verify.VStringSize;
import com.violet.domain.dto.DTO;
import com.violet.domain.exceptions.DTOVerifyException;
import com.violet.properties.DTOVerifyProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class DTOVerifyUtils {

    // 缓存类的字段数组，减少反射开销
    private static final Map<Class<?>, Field[]> fieldCache = new ConcurrentHashMap<>();

    private Field[] getFields(Class<?> clazz) {
        return fieldCache.computeIfAbsent(clazz, k -> {
            Field[] fields = clazz.getDeclaredFields();
            Arrays.stream(fields).forEach(field -> field.setAccessible(true));
            return fields;
        });
    }

    public void verify(DTO dto) {
        try {
            // 对 Dto 及其子字段的所有 Dto 进行校验
            Stack<DTO> stack = new Stack<>();
            stack.push(dto);
            while (!stack.isEmpty()) {
                DTO d = stack.pop();
                Field[] fields = this.getFields(d.getClass());
                this.baseVerify(d, fields);
                for (Field field : fields) {
                    Object object = field.get(d);
                    if (object instanceof DTO) stack.push((DTO) object);
                }
            }
        }
        catch (IllegalAccessException e) {throw new RuntimeException(e);}
    }

    private void baseVerify(DTO dto, Field[] fields) throws IllegalAccessException {
        // 遍历每一个字段
        for (Field field : fields) {

            // 如果有 @VNot 注解，则跳过
            if (field.getAnnotation(VNot.class) != null) continue;

            // 如果有 @VCanNull 注解，则判断是否可以为空，如果没有该字段，则表示一定不能为空
            if (field.getAnnotation(VCanNull.class) != null) this.vCanNull(dto, field);
            else this.vNotNull(dto, field);

            // 如果是字符串类型，都需要去掉两端空格
            if (field.getType() == String.class) {
                String value = (String) field.get(dto);
                field.set(dto, value.trim());
            }

            // 判断是否为手机号
            if (field.getAnnotation(VPhone.class) != null) this.vPhone(dto, field);

            // 对字符串长度校验
            if (field.getAnnotation(VStringSize.class) != null) this.vStringSize(dto, field);

            // 如果字段类型是List、Map集合或者数组，则不能是空的
            if (field.getType().isArray() || Map.class.isAssignableFrom(field.getType()) || List.class.isAssignableFrom(field.getType())) {
                // 数组长度校验
                vArray(dto, field);
                // 数组里是不是 DTO
                vArrayDto(dto, field);
            }
        }
    }

    private void vNotNull(DTO dto, Field field) throws IllegalAccessException {
        // 拿到字段值
        Object object = field.get(dto);
        // 判断是否为空
        if (object == null) {
            // 为空，抛出异常
            throw new DTOVerifyException(
                dto.getClass() + "." + field.getName() + " 不能为 null",
                field.getName() + ", 不能为空"
            );
        }
    }

    private void vCanNull(DTO dto, Field field) throws IllegalAccessException {
        // 拿到字段值
        Object object = field.get(dto);
        // 是否可以为空
        if (!field.getAnnotation(VCanNull.class).value()) {
            // 不可以为空
            if (object == null) {
                // 为空，抛出异常
                throw new DTOVerifyException(
                    dto.getClass() + "." + field.getName() + " 不能为 null",
                    field.getName() + ", 不能为空"
                );
            }
        }
    }

    private void vPhone(DTO dto, Field field) throws IllegalAccessException {
        // 字段类型必须是String类型
        if (field.getType() != String.class)
            throw new RuntimeException("@VStringSize 只能用于 String 类型的字段: " + dto.getClass() + "." + field.getName());

        // 拿到字段值
        String phone = (String) field.get(dto);
        // 判断是否为手机号
        if (!phone.matches(field.getAnnotation(VPhone.class).value())) {
            // 不是手机号，抛出异常
            throw new DTOVerifyException(
                dto.getClass() + "." + field.getName() + " 不是手机号: " + phone,
                "手机号码格式不正确"
            );
        }
    }

    private void vStringSize(DTO dto, Field field) throws IllegalAccessException {
        // 字段类型必须是String类型
        if (field.getType() != String.class)
            throw new RuntimeException("@VStringSize 只能用于 String 类型的字段: " + dto.getClass() + "." + field.getName());

        // 拿到字段值
        String value = (String) field.get(dto);
        // 拿到注解
        VStringSize vStringSize = field.getAnnotation(VStringSize.class);
        // 判断长度
        if (value.length() < vStringSize.min() || value.length() > vStringSize.max()) {
            // 长度不符合要求，抛出异常
            throw new DTOVerifyException(
                dto.getClass() + "." + field.getName() + " 长度不符合要求: " + value,
                field.getName() + ", 长度不符合要求，最小长度为 " + vStringSize.min() + ", 最大长度为 " + vStringSize.max()
            );
        }
    }

    private void vArray(DTO dto, Field field) throws IllegalAccessException {
        // 如果是数组
        if (field.getType().isArray()) {
            // 拿到数组长度
            int length = Array.getLength(field.get(dto));
            // 判断长度是否为0
            if (length == 0) {
                // 为0，抛出异常
                throw new DTOVerifyException(
                    dto.getClass() + "." + field.getName() + " 是个数组，长度不能为空",
                    field.getName() + ", 不能为空"
                );
            }
        }
        // 如果是List
        else if (List.class.isAssignableFrom(field.getType())) {
            // 拿到List长度
            int length = ((List<?>) field.get(dto)).size();
            // 判断长度是否为0
            if (length == 0) {
                // 为0，抛出异常
                throw new DTOVerifyException(
                    dto.getClass() + "." + field.getName() + " 是个 List，长度不能为空",
                    field.getName() + ", 不能为空"
                );
            }
        }
        // 如果是 Map
        else if (Map.class.isAssignableFrom(field.getType())) {
            // 拿到 Map 长度
            int length = ((Map<?, ?>) field.get(dto)).size();
            // 判断长度是否为0
            if (length == 0) {
                // 为0，抛出异常
                throw new DTOVerifyException(
                    dto.getClass() + "." + field.getName() + " 是个 Map，长度不能为空",
                    field.getName() + ", 不能为空"
                );
            }
        }
    }

    private void vArrayDto(DTO dto, Field field) throws IllegalAccessException {
        // 拿到集合
        List<?> list = (List<?>) field.get(dto);
        // 遍历集合
        for (Object o : list) {
            // 判断是否为 DTO 类型
            if (o instanceof DTO) {
                // 是 DTO 类型，递归校验
                this.verify((DTO) o);
            }
        }
    }
}
